package voidlib;
import com.sun.faces.util.CollectionsUtils.ConstMap;
import voidlib.VoidModel;
import voidlib.VoidUtils;



import java.util.HashMap;
import java.util.Map;

import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import javax.sql.RowSet;
import java.io.Console;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSetMetaData;
import java.sql.DriverManager;

import models.*;
import db.*;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import org.springframework.util.StringUtils;
import voidlib.UserRole;

//import org.apache.commons.lang.StringUtils;


/**
 * @author nevidimov
 */
public abstract class VoidMapper {
    
    protected abstract String _getTable();
    protected abstract String _getId();
    
    protected abstract Class<?> _getModel();
    
    protected static String[] _modelFields;
    
    protected static String _modelIdTitle;
    
    
    public VoidMapper() {
        _modelFields = getModelFields();
    }
    
    public String getId() {
        return _getId();
    }
    
    
    public int save(HashMap<String, Object> vals, String modelIdTitle) {
        
        //String idTitle = VoidUtils.toCamelCase(_getId());
        _modelIdTitle = modelIdTitle;
        if (!vals.containsKey(modelIdTitle) || vals.get(modelIdTitle).equals(-1)) {
            return this.create(vals);
        } else {
            return this.update(vals);
        }
    }
    
    public int create(HashMap<String, Object> vals) {
        vals.remove(_modelIdTitle);
        
        Connection conn = DbConnector.getConnection();
        Object[] keys = vals.keySet().toArray();
        
        StringBuilder b = new StringBuilder("insert into ");
        b.append(_getTable());
        b.append(" (");
        for (int i = 0; i < keys.length; i++) {
            b.append(VoidUtils.toUnderscore(keys[i].toString()));
            if (i < keys.length - 1) {
                b.append(", ");
            }
        }
        b.append(") values(?");
        for (int i = 1; i < keys.length; i++) {
            b.append(", ?");
        }
        b.append(")");
        try {
            PreparedStatement st = conn.prepareStatement(b.toString(), PreparedStatement.RETURN_GENERATED_KEYS);
            for (int i = 0; i < keys.length; i++) {
                st.setObject(i + 1, notPrimitiveToString(vals.get(keys[i])));
            }
            int res = st.executeUpdate();
            ResultSet generatedkeys = st.getGeneratedKeys();
            generatedkeys.next();
            int newId = generatedkeys.getInt(1);
            return newId;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }
    
    protected Object notPrimitiveToString(Object obj) {
        if (isWrapperType(obj.getClass())) {
            return obj;
        }
        return obj.toString();
    }
    
    public int update(HashMap<String, Object> vals) {
        int idValue = (Integer)vals.get(_modelIdTitle);
        vals.remove(_getId());
        
        Connection conn = DbConnector.getConnection();
        Object[] keys = vals.keySet().toArray();
        
        StringBuilder b = new StringBuilder("update ");
        b.append(_getTable());
        b.append(" set ");
        
        for (int i = 0; i < keys.length; i++) {
            b.append(VoidUtils.toUnderscore(keys[i].toString()));
            b.append("=");
            //b.append(vals.get(keys[i].toString()).toString());
            b.append("?");
            if (i < keys.length - 1) {
                b.append(", ");
            }
        }
        b.append(" where ");
        b.append(_getId());
        b.append("=");
        b.append(idValue);
        b.append("");
        try {
            PreparedStatement st = conn.prepareStatement(b.toString());
            for (int i = 0; i < keys.length; i++) {
                st.setObject(i + 1, notPrimitiveToString(vals.get(keys[i])));
            }
            int res = st.executeUpdate();
            return idValue;
        } catch (Exception e) {
            e.printStackTrace();;
        }
        return -1;
    }
    
    protected final String[] getModelFields()
    {
        Class<?> cl = this._getModel();
        Field[] fields = cl.getDeclaredFields();
        LinkedList<String> tmp = new LinkedList<String>();
        for (int i = 0; i < fields.length; i++) {
            String name = fields[i].getName();
            if (!name.startsWith("_")) {
                continue;
            }
            tmp.add(name.substring(1));
        }
        String[] res = new String[tmp.size()];
        int i = 0;
        for (String t : tmp) {
            res[i++] = t;
        }
        return res;
    }
    

    
    protected <T> T rowToModel(ResultSet set) 
    {
        try {
        Class<?> cl = this._getModel();
                VoidModel res = (VoidModel)cl.newInstance();
                for (int i = 0; i < _modelFields.length; i++) {
                    Object val = set.getObject(VoidUtils.toUnderscore(_modelFields[i]));
                    res.setField("_" + _modelFields[i], val);
                }
                return (T)res;
        } catch (Exception e) {
            e.printStackTrace();
                    
        }
        return null;
    }
    
    public <T> T fetch(int id)
    {
        
        String query = "select * from " + _getTable() + " where " + _getId() + " = " + id + "";
        ResultSet set = DbConnector.ExecuteQuery(query);
        try {
            if (set.next()) {
                T res = this.rowToModel(set);
                return res;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public <T> List<T> fetchAll(Object ... params) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < params.length; i += 2) {
            map.put((String)params[i], params[i + 1]);
        }
        return fetchAll(map);
    }
    
    public <T> List<T> fetchOne(Object ... params) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < params.length; i += 2) {
            map.put((String)params[i], params[i + 1]);
        }
        return fetchOne(map);
    }
    
    public <T> List<T> fetchAll(String cond, Object value) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put(cond, value);
        return fetchAll(map);
    }
    
    public <T> T fetchOne(Map<String, Object> where) {
        List<T> temp = this.fetchAll(where);
        return temp.get(0);
    }
    
    
    public <T> T fetchOne(String cond, Object value) {
        List<T> temp = this.fetchAll(cond, value);
        return temp.get(0);
    }
    
    public <T> List<T> fetchAll(Map<String, Object> where) {
        String query = "select * from " + _getTable();
        ResultSet set = null;
        if (where != null && where.size() > 0)
        {
            query += " where ";
            boolean first = true;
            for (String key : where.keySet()) {
                if (first) {
                    first = false;
                } else {
                    query += " and ";
                }
                query += key;
            }

            Object[] keys = where.keySet().toArray();
            try {
                PreparedStatement st = DbConnector.getConnection().prepareStatement(query);
                for (int i = 0; i < keys.length; i++) {
                    st.setObject(i + 1, notPrimitiveToString(where.get(keys[i].toString())));
                }
                
                set = st.executeQuery();
            } catch (Exception e) {
                e.printStackTrace();;
            }
        } else {
            set = DbConnector.ExecuteQuery(query);
        }
        
        LinkedList<T> res = new LinkedList<T>();
        if (set != null) {
            try {
                while (set.next()) {
                    T model = this.rowToModel(set);
                    res.add(model);
                } 
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return res;
    }
    
    public void delete(String cond, Object value) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put(cond, value);
        delete(map);
    }
    
    
    public void delete(Map<String, Object> where) {
        String query = "delete from " + _getTable();
        ResultSet set = null;
        if (where != null)
        {
            query += " where ";
            boolean first = true;
            for (String key : where.keySet()) {
                if (first) {
                    first = true;
                } else {
                    query += " and ";
                }
                query += key;
            }

            Object[] keys = where.keySet().toArray();
            try {
                PreparedStatement st = DbConnector.getConnection().prepareStatement(query);
                for (int i = 0; i < keys.length; i++) {
                    st.setObject(i + 1, notPrimitiveToString(where.get(keys[i].toString())));
                }
                st.executeUpdate();
            } catch (Exception e) {
                
            }
        } else {
            try {
                Statement st = DbConnector.getConnection().createStatement();
                st.executeUpdate(query);
            } catch (Exception e)  {}
        }
    }
    
    
    
    
    private static final HashSet<Class<?>> WRAPPER_TYPES = getWrapperTypes();

    public static boolean isWrapperType(Class<?> clazz)
    {
        return WRAPPER_TYPES.contains(clazz);
    }

    private static HashSet<Class<?>> getWrapperTypes()
    {
        HashSet<Class<?>> ret = new HashSet<Class<?>>();
        ret.add(Boolean.class);
        ret.add(Character.class);
        ret.add(Byte.class);
        ret.add(Short.class);
        ret.add(Integer.class);
        ret.add(Long.class);
        ret.add(Float.class);
        ret.add(Double.class);
        ret.add(Void.class);
        return ret;
    }
}
